{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "import pandas as pd\n",
    "import numpy as np\n",
    "\n",
    "import sys\n",
    "\n",
    "import pickle \n",
    "import scipy.io as sio\n",
    "\n",
    "import scipy.spatial.distance as ssd\n",
    "from numpy.random import random\n",
    "import json"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "#基于用户的协同过滤\n",
    "def user_CF_pred(uid, iid, similarity_metricx):\n",
    "    sim_accumulate = 0.0\n",
    "    rat_acc =0  \n",
    "    users_mu = mu\n",
    "    \n",
    "    for user_id in item_users[iid]:\n",
    "        sim = similarity_metricx[user_id, uid]\n",
    "        \n",
    "        if 0 != sim:\n",
    "            rat_acc += sim * (user_item_scores[user_id, iid] - users_mu[user_id])\n",
    "            sim_accumulate += np.abs(sim)\n",
    "            \n",
    "    if 0 != sim_accumulate:\n",
    "        score = users_mu[uid] + rat_acc/sim_accumulate\n",
    "    else:\n",
    "        score = users_mu[uid]\n",
    "        \n",
    "    return score  "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "#基于item的协同过滤\n",
    "def item_CF_pred(uid, iid, similarity_metricx, n_Knns):\n",
    "    sim_accumulate = 0.0\n",
    "    rat_acc =0\n",
    "    n_nn_items = 0\n",
    "    users_mu = mu\n",
    "    \n",
    "    #相似度排序\n",
    "    cur_similarity_metricx = np.array(similarity_metricx[iid,:])\n",
    "    cur_similarity_metricx = cur_similarity_metricx.flatten()\n",
    "    \n",
    "    sort_index = sorted(((e,i) for e, i in enumerate(list(cur_similarity_metricx))), reverse = True)\n",
    "    for i in range(0, len(sort_index)):\n",
    "#         print(\"sort_index\",sort_index)\n",
    "        \n",
    "        cur_item_index = sort_index[i][0]\n",
    "        \n",
    "        if n_nn_items >= n_Knns:\n",
    "            break;\n",
    "            \n",
    "        if cur_item_index in user_items[uid]:\n",
    "#             print(\"iid\",iid)\n",
    "#             print(\"cur_item_index\",cur_item_index)\n",
    "            \n",
    "#             print(\"similarity_metricx\",similarity_metricx)\n",
    "#             print(\"similarity_metricx[iid, cur_item_index]\",similarity_metricx[iid, cur_item_index])\n",
    "            \n",
    "            sim = similarity_metricx[iid, cur_item_index]\n",
    "        \n",
    "            if 0 != sim:\n",
    "                rat_acc += sim * (user_item_scores[uid, cur_item_index])\n",
    "                sim_accumulate += np.abs(sim)\n",
    "            \n",
    "            n_nn_items += 1\n",
    "            \n",
    "    if 0 != sim_accumulate:\n",
    "        score = users_mu[uid] + rat_acc/sim_accumulate\n",
    "    else:\n",
    "        score = users_mu[uid]\n",
    "        \n",
    "    if score < 0:\n",
    "        score = 0\n",
    "        \n",
    "    return score\n",
    "        "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "#基于模型SVD的协同过滤\n",
    "def svd_CF_pred(uid, iid):\n",
    "    score = mu[uid] + bi[iid] + bu[uid] + np.sum(qi[iid] * pu[uid])\n",
    "    return score"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "#用户和item索引\n",
    "users_index = pickle.load(open('users_index.pkl', 'rb'))\n",
    "items_index = pickle.load(open('items_index.pkl', 'rb'))\n",
    "\n",
    "n_user = len(users_index)\n",
    "n_items = len(items_index)\n",
    "\n",
    "#用户-物品关系矩阵\n",
    "user_item_scores = sio.mmread(\"user_item_scores\").todense()\n",
    "\n",
    "#倒排表\n",
    "#每个用户播放的歌曲\n",
    "user_items = pickle.load(open('user_items.pkl', 'rb'))\n",
    "#每个事件参与的用户\n",
    "item_users = pickle.load(open('item_users.pkl', 'rb'))\n",
    "\n",
    "#用户之间的相似度\n",
    "similarity_matrix_users = pickle.load(open('user_similarity_played.pkl', 'rb'))\n",
    "\n",
    "#所有item的相似度\n",
    "similarity_matrix_items = pickle.load(open('item_similarity_played.pkl', 'rb'))\n",
    "\n",
    "#训练好的\n",
    "with open('svd_model.json','r') as file:\n",
    "    dict_ = json.load(file)\n",
    "\n",
    "    mu = dict_['mu']\n",
    "    K = dict_['K']\n",
    "\n",
    "    bi = np.array(dict_['bi']) \n",
    "    bu = np.array(dict_['bu'] )\n",
    "    qi = np.array(dict_['qi'] )\n",
    "    pu = np.array(dict_['pu'] )\n",
    "     "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "#推荐\n",
    "N_KNNS = 10\n",
    "\n",
    "def recommend(user, flag = 0):\n",
    "    cur_user_id = users_index[user]\n",
    "    \n",
    "    #从倒排表中 训练集中 寻找该用户打分过的item\n",
    "    cur_user_items = user_items[cur_user_id]\n",
    "    \n",
    "    #该用户对所有item的打分\n",
    "    user_items_scores = np.zeros(n_items)\n",
    "    \n",
    "    #预测打分\n",
    "    for i in range(n_items):\n",
    "        if i not in cur_user_items:#训练中没打过分的\n",
    "            if(0 == flag):\n",
    "                user_items_scores[i] = user_CF_pred(cur_user_id, i, similarity_matrix_users)  \n",
    "            elif(1 == flag):\n",
    "                user_items_scores[i] = item_CF_pred(cur_user_id, i, similarity_matrix_items, N_KNNS)\n",
    "            else:\n",
    "                user_items_scores[i] = svd_CF_pred(cur_user_id, i)       \n",
    "\n",
    "    #推荐\n",
    "    sort_index = sorted(((e,i) for e, i in enumerate(list(user_items_scores))), reverse = True)\n",
    "  \n",
    "    columns = ['item_id', 'score']\n",
    "    df = pd.DataFrame(columns=columns) \n",
    "\n",
    "    for i in range(len(sort_index)):\n",
    "        cur_item_index = sort_index[i][0]  \n",
    "        cur_item = list(items_index.keys())[list(items_index.values()).index(cur_item_index)]\n",
    "        \n",
    "        if ~np.isnan(sort_index[i][0]) and cur_item_index not in cur_user_items:\n",
    "            df.loc[len(df)] = [cur_item, sort_index[i][1]]\n",
    "    \n",
    "    return df"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(7504, 6)"
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "#读取测试数据\n",
    "dpath = ''\n",
    "df_triplet_test = pd.read_csv(dpath + 'triplet_dataset_sub_test.csv')\n",
    "\n",
    "# #随机取样\n",
    "# df_triplet_test = df_triplet_test.sample(n=100, frac=None, replace=False, weights=None, random_state=None, axis=None)\n",
    "# df_triplet_test = df_triplet_test.head(100)\n",
    "df_triplet_test.shape"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "#测试 计算评价指标\n",
    "# PR RMSE、覆盖度\n",
    "def val(flag = 0):\n",
    "    unique_users_test = df_triplet_test['user'].unique()\n",
    "    N_RS_ITEMS = 20\n",
    "\n",
    "    n_hits = 0\n",
    "    n_total_rec_items = 0\n",
    "    n_test_items = 0\n",
    "\n",
    "    all_rec_items = set()\n",
    "    rss_test = 0\n",
    "\n",
    "    #对每个用户测试\n",
    "    for user in unique_users_test:\n",
    "        if user not in users_index:#user 在训练集中 没有出现 新用户不能用协同过滤\n",
    "            print(\"%s is a new user\"%(str(user)))\n",
    "            continue\n",
    "\n",
    "        df_user_records_test = df_triplet_test[df_triplet_test.user == user]\n",
    "\n",
    "        df_rec_items = recommend(user, flag)\n",
    "        for i in range(N_RS_ITEMS):\n",
    "\n",
    "            item = df_rec_items.iloc[i]['item_id']\n",
    "\n",
    "            if item in df_user_records_test['song'].values:\n",
    "                n_hits += 1\n",
    "            all_rec_items.add(item)\n",
    "\n",
    "        #计算RMSE   \n",
    "        for i in range(df_user_records_test.shape[0]):\n",
    "\n",
    "            item = df_user_records_test.iloc[i]['song']\n",
    "            score = df_user_records_test.iloc[i]['fractional_play_count']\n",
    "\n",
    "            dfi = df_rec_items[df_rec_items.item_id == item]\n",
    "            if (0 == dfi.shape[0]):\n",
    "                continue\n",
    "\n",
    "            pred_score = dfi['score'].values[0]\n",
    "\n",
    "            rss_test += (pred_score - score)**2 #残差平方和\n",
    "\n",
    "            n_total_rec_items += N_RS_ITEMS\n",
    "\n",
    "            n_test_items += df_user_records_test.shape[0]\n",
    "\n",
    "    #计算 percision  和 recall\n",
    "    if 0!= n_total_rec_items:\n",
    "        percision = n_hits / (1.0 * n_total_rec_items)\n",
    "\n",
    "    if 0 != n_test_items:\n",
    "        recall = n_hits / (1.0 * n_test_items)\n",
    "\n",
    "    #覆盖度\n",
    "    if 0 != n_items:\n",
    "        coverage = len(all_rec_items) / (1.0 * n_items)\n",
    "\n",
    "    #打分的均方误差\n",
    "    if 0 != df_triplet_test.shape[0]:\n",
    "        rmse = np.sqrt(rss_test / df_triplet_test.shape[0])   \n",
    "\n",
    "    print(\"percision:\", percision)\n",
    "    print(\"recall:\", recall)\n",
    "    print(\"coverage:\", coverage)\n",
    "    print(\"rmse:\", rmse)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1.基于用户相似度评价\n",
      "8acf4c54a3184e0ff1c4b0655c71f6e0b79906b4 is a new user\n",
      "af3ee32357049dd96231238bd1b019e8142ee6aa is a new user\n",
      "percision: 0.00068\n",
      "recall: 0.0006979417560761988\n",
      "coverage: 0.03125\n",
      "rmse: 0.05126827670216608\n",
      "\n",
      "2.基于歌曲相似度评价\n",
      "8acf4c54a3184e0ff1c4b0655c71f6e0b79906b4 is a new user\n",
      "af3ee32357049dd96231238bd1b019e8142ee6aa is a new user\n",
      "percision: 0.00068\n",
      "recall: 0.0006979417560761988\n",
      "coverage: 0.03125\n",
      "rmse: 0.07247181566806309\n",
      "\n",
      "3.基于SVD模型相似度评价\n",
      "8acf4c54a3184e0ff1c4b0655c71f6e0b79906b4 is a new user\n",
      "af3ee32357049dd96231238bd1b019e8142ee6aa is a new user\n",
      "percision: 0.00068\n",
      "recall: 0.0006979417560761988\n",
      "coverage: 0.03125\n",
      "rmse: 0.30430545371964923\n"
     ]
    }
   ],
   "source": [
    "for flag in range(3):\n",
    "    \n",
    "    if(0 == flag):\n",
    "        print(\"1.基于用户相似度评价\")\n",
    "    elif(1 == flag):\n",
    "        print(\"\\n2.基于歌曲相似度评价\")\n",
    "    else:\n",
    "        print(\"\\n3.基于SVD模型相似度评价\")\n",
    "    val(flag)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.0"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
